// xjdklog
package main

import (
	"bufio"
	"bytes"
	"color"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"sort"
	"strings"

	"github.com/bitly/go-simplejson"
)

var jdkoutmap = make(map[string]int64)
var stderrmap = make(map[string]int64)
var stdoutmap = make(map[string]int64)
var othermap = make(map[string]int64)

var excludemap = make(map[string]int64)

func isFile(fileName string) bool {
	if _, err := os.Stat(fileName); err == nil {
		return true
	}
	return false
}

func ReadConfig(filepath string) *simplejson.Json {
	res, _ := ioutil.ReadFile(filepath)
	js, err := simplejson.NewJson(res)
	if err != nil {
		fmt.Println(err)
	}
	return js
}

//统计各个key的数量
func Countjdkout(line string, js *simplejson.Json) {
	jsmap, _ := js.Get("jdkout").Map()
	for c, d := range jsmap {
		//fmt.Printf("%s:%s\n", c, d)
		if strings.Contains(line, fmt.Sprintf("%s", d)) {
			v, ok := jdkoutmap[c]
			if ok {
				jdkoutmap[c] = v + 1
			} else {
				jdkoutmap[c] = 1
			}
		}
	}
}

func Countstderr(line string, js *simplejson.Json) {
	jsmap, _ := js.Get("stderr").Map()
	for c, d := range jsmap {
		//fmt.Printf("%s:%s\n", c, d)
		if strings.Contains(line, fmt.Sprintf("%s", d)) {
			v, ok := stderrmap[c]
			if ok {
				stderrmap[c] = v + 1
			} else {
				stderrmap[c] = 1
			}
		}
	}
}

func Countstdout(line string, js *simplejson.Json) {
	jsmap, _ := js.Get("stdout").Map()
	for c, d := range jsmap {
		//fmt.Printf("%s:%s\n", c, d)
		if strings.Contains(line, fmt.Sprintf("%s", d)) {
			v, ok := stdoutmap[c]
			if ok {
				stdoutmap[c] = v + 1
			} else {
				stdoutmap[c] = 1
			}
		}
	}
}

func CountOther(line string, js *simplejson.Json) {
	jsmap, _ := js.Get("other").Map()
	for c, d := range jsmap {
		//fmt.Printf("%s:%s\n", c, d)
		if strings.Contains(line, fmt.Sprintf("%s", d)) {
			v, ok := othermap[c]
			if ok {
				othermap[c] = v + 1
			} else {
				othermap[c] = 1
			}
		}
	}
}

func PrintKeyStr(line, ks string, pline bool, ic int64) {
	//fmt.Println(ifcs, ic, line)
	line1 := strings.Replace(line, "\n", "", -1)
	if pline {
		if strings.Contains(line1, ks) {
			fmt.Printf("%-0.300s\n", line1)
			ct = ct + 1
		}
		if ct > ic {
			os.Exit(1)
		}
	} else {
		if strings.Contains(line1, ks) {
			fmt.Printf("%-0.300s\n", line1)
			ifcs = true
		}
		if ifcs {
			fmt.Printf("%-0.300s\n", line1)
			ct = ct + 1
		}

		if ct > ic {
			os.Exit(1)
		}
	}
	//fmt.Println(ifcs, ic, ct)
}

//排序
type CountItem struct {
	K string
	V int64
}

// 以字符串形式显示数据项
func (c CountItem) String() string {
	return fmt.Sprintf("%v: %d", c.K, c.V)
}

// 排序数据结构
type ByCount []CountItem

func (c ByCount) Len() int           { return len(c) }
func (c ByCount) Swap(i, j int)      { c[i], c[j] = c[j], c[i] }
func (c ByCount) Less(i, j int) bool { return c[i].V > c[j].V }

type TrendCounter struct {
	MapOom        map[string]int64
	MapRs         map[string]int64
	MapJdbc       map[string]int64
	MapMc         map[string]int64
	MapR          map[string]int64
	MapHttpClient map[string]int64
}

func NewTrendCounter() (c *TrendCounter) {
	c = &TrendCounter{
		MapOom:        make(map[string]int64, 60),
		MapRs:         make(map[string]int64, 60),
		MapJdbc:       make(map[string]int64, 60),
		MapMc:         make(map[string]int64, 60),
		MapR:          make(map[string]int64, 60),
		MapHttpClient: make(map[string]int64, 60),
	}
	return
}

//按照时间进行统计
func (*TrendCounter) Count(line string, js *simplejson.Json) {
	rsstart, _ := js.Get("jdkout").Get("ResinStart").String()
	fmt.Println(rsstart)
}

func str2Pattern(strinfo string) string {
	var buf bytes.Buffer
	line := strings.Replace(string(strinfo), "\n", "", -1)
	runes := []rune(line)
	last := '/'
	for _, r := range runes {
		if r >= '0' && r <= '9' {
			if islastint {
				buf.WriteString("")
			} else {
				if last < '0' || last > '9' {
					buf.WriteString("[0-9]")
					islastint = true
				}
			}
		} else {
			buf.WriteRune(r)
			islastint = false
		}
		last = r
	}
	return buf.String()
}

func isSlash(r rune) bool {
	return r == '}' || r == '@' || r == ']'
}

func GroupbySortStr(line, logfile string, excludestr bool, js *simplejson.Json) {
	//infostr := strings.Split(line, "}|@")[1]
	//infostr := strings.FieldsFunc(line, isSlash)[1]

	var jsarr, jsarr2, jsother, jsother2 []string
	var err, err2 error
	if strings.Contains(logfile, "jdk-out") {
		jsarr, err = js.Get("exclude").Get("excludejdkstr").StringArray()
		jsarr2, err2 = js.Get("exclude").Get("includejdkstr").StringArray()
	} else if strings.Contains(logfile, "stderr") {
		jsarr, err = js.Get("exclude").Get("excludestderrstr").StringArray()
		jsarr2, err2 = js.Get("exclude").Get("includestderrstr").StringArray()
	} else if strings.Contains(logfile, "stdout") {
		jsarr, err = js.Get("exclude").Get("excludestdoutstr").StringArray()
		jsarr2, err2 = js.Get("exclude").Get("includestdoutstr").StringArray()
	} else {
		jsjdk, _ := js.Get("exclude").Get("excludejdkstr").StringArray()
		jsjdk2, _ := js.Get("exclude").Get("includejdkstr").StringArray()

		jsstderr, _ := js.Get("exclude").Get("excludestderrstr").StringArray()
		jsstderr2, _ := js.Get("exclude").Get("includestderrstr").StringArray()

		jsstdout, _ := js.Get("exclude").Get("excludestdoutstr").StringArray()
		jsstdout2, _ := js.Get("exclude").Get("includestdoutstr").StringArray()

		jsother, err = js.Get("exclude").Get("excludeotherstr").StringArray()
		jsother2, err2 = js.Get("exclude").Get("includeotherstr").StringArray()
		jsarr = ArrMerge(jsjdk, jsstderr, jsstdout, jsother)
		jsarr2 = ArrMerge(jsjdk2, jsstderr2, jsstdout2, jsother2)
	}

	if err != nil {
		fmt.Println("get exclude str fail1")
		return
	}
	if err2 != nil {
		fmt.Println("get include str fail1")
		return
	}

	var isecs bool = false
	var isics bool = false
	//exclude
	for _, v := range jsarr {
		if strings.Contains(line, v) {
			isecs = true
			continue
		}
	}

	//include
	for _, v := range jsarr2 {
		if strings.Contains(line, v) {
			isics = true
			continue
		}
	}

	//是否排除包含exlude内字符的行，并且不排除include内字符的行
	if excludestr {
		if isecs && !isics {
			return
		}
	}

	infostr := strings.Fields(line)
	v, ok := excludemap[str2Pattern(fmt.Sprintf("%s", infostr[2:]))]
	if ok {
		excludemap[str2Pattern(fmt.Sprintf("%s", infostr[2:]))] = v + 1
	} else {
		excludemap[str2Pattern(fmt.Sprintf("%s", infostr[2:]))] = 1
	}
}

//将多个数组合并成一个数组
func ArrMerge(arry ...[]string) []string {
	var strarry []string
	for _, num := range arry {
		for _, cc := range num {
			strarry = append(strarry, cc)
		}
	}
	return strarry
}

func PrintLine(line, logfile string, js *simplejson.Json) {
	var jsarr, jsarr2, jsother, jsother2 []string
	var err, err2 error
	if strings.Contains(logfile, "jdk-out") {
		jsarr, err = js.Get("exclude").Get("excludejdkstr").StringArray()
		jsarr2, err2 = js.Get("exclude").Get("includejdkstr").StringArray()
	} else if strings.Contains(logfile, "stderr") {
		jsarr, err = js.Get("exclude").Get("excludestderrstr").StringArray()
		jsarr2, err2 = js.Get("exclude").Get("includestderrstr").StringArray()
	} else if strings.Contains(logfile, "stdout") {
		jsarr, err = js.Get("exclude").Get("excludestdoutstr").StringArray()
		jsarr2, err2 = js.Get("exclude").Get("includestdoutstr").StringArray()
	} else {
		//jsarr, err = js.Get("exclude").Get("excludeotherstr").StringArray()
		//jsarr2, err2 = js.Get("exclude").Get("includeotherstr").StringArray()
		jsjdk, _ := js.Get("exclude").Get("excludejdkstr").StringArray()
		jsjdk2, _ := js.Get("exclude").Get("includejdkstr").StringArray()

		jsstderr, _ := js.Get("exclude").Get("excludestderrstr").StringArray()
		jsstderr2, _ := js.Get("exclude").Get("includestderrstr").StringArray()

		jsstdout, _ := js.Get("exclude").Get("excludestdoutstr").StringArray()
		jsstdout2, _ := js.Get("exclude").Get("includestdoutstr").StringArray()

		jsother, err = js.Get("exclude").Get("excludeotherstr").StringArray()
		jsother2, err2 = js.Get("exclude").Get("includeotherstr").StringArray()
		jsarr = ArrMerge(jsjdk, jsstderr, jsstdout, jsother)
		jsarr2 = ArrMerge(jsjdk2, jsstderr2, jsstdout2, jsother2)
	}

	if err != nil {
		fmt.Println("get exclude str fail1")
		return
	}
	if err2 != nil {
		fmt.Println("get include str fail1")
		return
	}

	var isecs bool = false
	var isics bool = false
	//exclude
	for _, v := range jsarr {
		if strings.Contains(line, v) {
			isecs = true
			continue
		}
	}
	if !isecs {
		ss := strings.Replace(line, "\n", "", -1)
		fmt.Printf("%-0.300s\n", ss)
	}

	//include
	for _, v := range jsarr2 {
		if strings.Contains(line, v) {
			isics = true
			continue
		}
	}

	if isics {
		ss := strings.Replace(line, "\n", "", -1)
		fmt.Printf("%-0.300s\n", ss)
	}
}

var js *simplejson.Json

//统计显示了多少行
var ct int64
var ifcs bool

//是否是数字
var islastint bool
var VERSION string

func Help() {
	//color.Enable()
	fmt.Printf("Version: v1.0-" + VERSION)
	fmt.Printf("\nauthor:kang")
	color.Cyan("\nExample Usage:\n")
	//fmt.Println("\nUsage:\n")
	fmt.Println("\txjdklog jdk-out.log")
	fmt.Println("\txjdklog -k \"Communications link failure\" jdk-out.log")
	fmt.Println("\txjdklog -l -top 40 -k \"Communications link failure\" jdk-out.log")
	fmt.Println("\txjdklog -l -top 40 jdk-out.log")
	fmt.Println("\txjdklog -g jdk-out.log")
	fmt.Println("\txjdklog -e=false -g jdk-out.log")
	color.Cyan("\nParameter:\n")
	fmt.Println("Default CfgPath:/var/PROGRAM/MANAGEMENT/modules/xbash/xjdklog.json")
	fmt.Println("\t-f         specify config path")
	fmt.Println("\t-s         print key summary")
	fmt.Println("\t-g         group by log")
	fmt.Println("\t-e         Whether exclude Especially string,default is true")
	fmt.Println("\t-k         view Especially key")
	fmt.Println("\t-top       print top line,default:30,use with -k")
	fmt.Println("\t-l         list important info,or use with -k")
	fmt.Println("\t-h         show help")
}

func main() {
	help := flag.Bool("h", false, "show help")
	cfile := flag.String("f", "", "config path")
	summary := flag.Bool("s", false, "jdk-out.log,stderr.log,stdout.log")
	groupby := flag.Bool("g", false, "groupby")
	excludestr := flag.Bool("e", true, "exclude nouse string")
	printline := flag.Bool("l", false, "printline")
	kstr := flag.String("k", "", "View a class of information")
	topline := flag.Int64("top", 30, "print top line")
	//trend := flag.Bool("t", false, "sort time")
	flag.Parse()

	color.Enable()

	if *help {
		Help()
		return
	}

	if flag.NFlag() == 0 {
		*summary = true
	}

	if *cfile == "" {
		*cfile = "/var/PROGRAM/MANAGEMENT/modules/xbash/xjdklog.json"
	}
	js = ReadConfig(*cfile)

	logfile := flag.Arg(0)

	var file *os.File
	if flag.Arg(0) != "" {
		var err interface{}
		file, err = os.Open(logfile)
		if err != nil {
			fmt.Println(err)
			return
		}
	} else {
		file = os.Stdin
	}
	defer file.Close()

	// 建立行式文件缓冲流
	bi := bufio.NewReader(file)
	for {
		line, err := bi.ReadString('\n')
		if err != nil {
			break // 读取完成整个文件退出循环
		}
		//打印指定字符以下多少行，kstr指定字符，infoc打印的行数
		if *kstr != "" {
			PrintKeyStr(line, *kstr, *printline, *topline)
		}
		//排除特定的字符的行并排序top显示
		if *groupby {
			GroupbySortStr(line, logfile, *excludestr, js)
		}

		if *printline && *kstr == "" {
			PrintLine(line, logfile, js)
		}

		//统计各种特定字符出现错误的次数
		if *summary {
			if strings.Contains(logfile, "jdk-out") {
				Countjdkout(line, js)
			} else if strings.Contains(logfile, "stderr") {
				Countstderr(line, js)
			} else if strings.Contains(logfile, "stdout") {
				Countstdout(line, js)
			} else {
				Countjdkout(line, js)
				Countstderr(line, js)
				Countstdout(line, js)
				CountOther(line, js)
			}
		}

	}

	//print

	if *summary {
		switch {
		case strings.Contains(logfile, "jdk-out"):
			color.Cyan("=====jdk-out========\n")
			for aa, bb := range jdkoutmap {
				fmt.Printf("%s=%d\n", aa, bb)
			}
			color.Red("\n=====notes================\n")
			jsmap, _ := js.Get("jdkout").Map()
			for c, d := range jsmap {
				color.Cyan("%s:", c)
				fmt.Printf(" \"%s\"\n", d)
			}
		case strings.Contains(logfile, "stderr"):
			color.Cyan("=====stderr========\n")
			for cc, dd := range stderrmap {
				fmt.Printf("%s=%d\n", cc, dd)
			}
			color.Red("\n=====notes================\n")
			jsmap, _ := js.Get("stderr").Map()
			for c, d := range jsmap {
				color.Cyan("%s:", c)
				fmt.Printf(" \"%s\"\n", d)
			}
		case strings.Contains(logfile, "stdout"):
			color.Cyan("=====stdout========\n")
			for ee, ff := range stdoutmap {
				fmt.Printf("%s=%d\n", ee, ff)
			}
			color.Red("\n=====notes================\n")
			jsmap, _ := js.Get("stdout").Map()
			for c, d := range jsmap {
				color.Cyan("%s:", c)
				fmt.Printf(" \"%s\"\n", d)
			}
		default:
			color.Cyan("=====jdk-out.log====\n")
			for aa, bb := range jdkoutmap {
				fmt.Printf("%s=%d\n", aa, bb)
			}
			color.Cyan("=====stderr.log=====\n")
			for cc, dd := range stderrmap {
				fmt.Printf("%s=%d\n", cc, dd)
			}
			color.Cyan("=====stdout.log=====\n")
			for ee, ff := range stdoutmap {
				fmt.Printf("%s=%d\n", ee, ff)
			}
			color.Cyan("=====other.log======\n")
			for gg, hh := range othermap {
				fmt.Printf("%s=%d\n", gg, hh)
			}
		}
	}
	if *groupby {
		var rb ByCount = make([]CountItem, 0, len(excludemap))
		for k, v := range excludemap {
			rb = append(rb, *&CountItem{k, v})
		}
		sort.Sort(rb)
		for _, v := range rb {
			//fmt.Printf("%d   ", v.V)
			color.Cyan("%d   ", v.V)
			fmt.Printf("%s\n", v.K)
		}
	}
}
